package swagger_plugin

import (
	"embed"
	_ "embed"
	"gitee.com/kristas/booting-go/framework/common/util/lang"
	"gitee.com/kristas/booting-go/framework/core/bean"
	"gitee.com/kristas/booting-go/framework/core/statement/types"
	"gitee.com/kristas/booting-go/framework/web"
	"gitee.com/kristas/booting-go/framework/web/controller"
	"gitee.com/kristas/booting-go/framework/web/resp"
	"gitee.com/kristas/booting-go/framework/web/rest"
	"github.com/savaki/swag"
	"github.com/savaki/swag/endpoint"
	"github.com/savaki/swag/swagger"
	"net/http"
	"reflect"
)

type Starter struct {
	types.Controller `name:"swagger_starter" group:""`
	AppName          string           `value:"application.app_name"`
	ui               rest.GET         `url:"/swagger-ui.html"`
	static           web.FS           `url:"/swagger"`
	openAPI          rest.GET         `url:"/v2/open_api" param:"[r request,w response]"`
	Conf             *SwaggerConfig   `wire:"swagger_config"`
	Controllers      []web.Controller `wire:""`
}

func NewStarter() *Starter {
	return &Starter{}
}

func (r *Starter) Static() string {
	return "resources/swagger-ui/dist"
}

//go:embed assets/swagger-index.html
var swaggerIndex embed.FS

func (r *Starter) Ui() resp.Response {
	mv := resp.Model().SetFS(swaggerIndex, "assets/swagger-index.html")
	mv.SetAttributes("url", "/v2/open_api")
	mv.SetAttributes("prefix", "/swagger")
	mv.SetAttributes("title", r.AppName+" Swagger UI")
	return mv.Response()
}

func (r *Starter) OpenAPI(req *http.Request, w http.ResponseWriter) {
	var endpoints []*swagger.Endpoint
	for _, ctr := range r.Controllers {
		if bean.GetBeanName(ctr) == "swagger_starter" {
			continue
		}
		controller.RestApiExtract(ctr, func(apiInfo web.ApiInfo, svm web.ServiceMethod) {
			endpoints = append(endpoints, r.newEndpoint(ctr, apiInfo, svm))
		})
	}
	conf := r.Conf
	swag.New(
		swag.Endpoints(endpoints...),
		swag.Description(conf.Description),
		swag.Title(conf.Title),
		swag.Version(conf.Version),
		swag.TermsOfService(conf.TermsOfService),
		swag.ContactEmail(conf.Contact.Email),
		swag.License(conf.License.Name, conf.License.URL),
		swag.BasePath(conf.BasePath),
		swag.Schemes(conf.Schemes...),
		swag.Host(conf.Host),
		//swag.Security("petstore_auth", "read:pets"),
		//swag.SecurityScheme("petstore_auth",
		//	swagger.OAuth2Security("accessCode", "http://example.com/oauth/authorize", "http://example.com/oauth/token"),
		//	swagger.OAuth2Scope("write:pets", "modify pets in your account"),
		//	swagger.OAuth2Scope("read:pets", "read your pets"),
		//),
	).Handler(false).ServeHTTP(w, req)
}

func (r *Starter) newEndpoint(ctr web.Controller, apiInfo web.ApiInfo, svm web.ServiceMethod) *swagger.Endpoint {
	var options = []endpoint.Option{
		endpoint.Tags(lang.NewString(bean.GetBeanName(ctr)).SnakeCase().String()),
	}
	options = append(options, r.newRequestParams(apiInfo)...)
	options = append(options, r.newResponseParams(svm)...)
	return endpoint.New(apiInfo.GetHttpMethod(), apiInfo.GetURL(), apiInfo.GetDescription(), options...)
}

func (r *Starter) newRequestParams(apiInfo web.ApiInfo) (params []endpoint.Option) {
	for _, p := range apiInfo.GetParams() {
		switch p.GetSource() {
		case web.Path:
			if p.GetType().Kind() == reflect.Struct {
				params = append(params, endpointPathBody(p.GetType())...)
			} else {
				params = append(params, endpointPathType(p.GetType(), p.GetName(), ""))
			}
		case web.Query:
			if p.GetType().Kind() == reflect.Struct {
				params = append(params, endpointQueryBody(p.GetType())...)
			} else {
				params = append(params, endpointQueryType(p.GetType(), p.GetName(), ""))
			}
		case web.Body:
			params = append(params, endpoint.BodyType(p.GetType(), "", true))
		case web.Header:
			params = append(params, endpointHeaderType(p.GetName(), p.GetType(), ""))
		}
	}
	return
}

var errorResponse = []endpoint.Option{
	endpoint.Response(http.StatusInternalServerError, resp.ErrorResponse{}, http.StatusText(http.StatusInternalServerError)),
	endpoint.Response(http.StatusBadRequest, resp.ErrorResponse{}, http.StatusText(http.StatusBadRequest)),
}

type DynamicResult struct{}

func (r *Starter) newResponseParams(svm web.ServiceMethod) (params []endpoint.Option) {
	typ := svm.GetMethod().Type
	for i := 0; i < typ.NumOut(); i++ {
		out := typ.Out(i)
		if out.Implements(reflect.TypeOf(new(error)).Elem()) {
			params = append(params, errorResponse...)
		} else if out.Kind() != reflect.Interface {
			params = append(params, endpoint.ResponseType(http.StatusOK, out, http.StatusText(http.StatusOK)))
		} else {
			params = append(params, endpoint.Response(http.StatusOK, DynamicResult{}, http.StatusText(http.StatusOK)))
		}
	}
	return
}
